Google公司的软件工程之道 (1)
徐文锦 杨晓慧 朱少民 编译
英文原文由Google软件工程师Fergus Henderson 于2017年1月31日所写,文章末尾有作者介绍、原文链接。
Google(谷歌)已经是一个非常成功的公司,除了谷歌搜索和AdWords的成功之外,谷歌还向我们提供许多优秀的产品,如谷歌地图、翻译、语音识别、Chrome和Android等。谷歌还通过兼并小公司,拥有像阿尔法狗、YouTube等产品,并展示了一些尚未推出的惊人产品,例如自动驾驶汽车等。
谷歌的成功有很多原因,包括开明的领导力、优秀的人才、招人高门槛,以及有良好的财务实力可以成功利用快速成长的市场中的早期领导者。但其中一个不可忽视的原因是谷歌不断积累了优秀的软件工程实践,助成功一臂之力。借助许多全球最有才华的软件工程师的积累和智慧的结晶,随着时间的推移而不断提升软件工程实践。
【本文将比较彻底、全面地分享谷歌公司的软件工程之道, 总共分为三部分,分四次发布:
Google软件开发之道(1)(2)
Google软件项目管理之道
Google人员管理之道
全面呈现,细节是魔鬼,值得大家仔细阅读】
第1部分 Google软件开发之道
1. 如何管理源代码?()
大部分的代码都用统一的源代码库(repository)来管理,允许所有Google的软件工程师访问。同时也有以下几种值得注意的例外情况,如:
像大型开源项目Chrome和Android使用了独立的开源代码库
一些高价值的或高安全性的代码,则设定了严格的读取权限
但是大多数的程序依然是共享同一个代码库。截止到2015年1月,这个86TB的代码库已经存有10亿多个文件,其中有900多万源代码文件,其代码行数高达20亿,并包含了3500万代码提交(commits/check in)记录,每天还以4万次提交在增长(译者注:谷歌有超过3万开发人员)。对代码库的写权限是被限定的:只有列出的每个库subtree(译者注:Git 的术语)的所有者(Owner,译者注:本来也可以不翻译)有权批准对subtree的修改。但一般来说,任意一位工程师都可以访问任何代码,可以检出(check out)所需代码并进行构建,也可以在本地对其修改、测试, 并且经代码的所有者复审/评审(review)通过后提交(check in)新修改的代码。谷歌的企业文化鼓励工程师跨越项目边界去修复他们认为受到破坏并知道如何修复的任何缺陷。这给了工程师更多授权,并促使达到高质量的基础设施,以更好地满足其使用需求。
几乎所有的开发均发生在代码库的“head”上(译者注:git中的head,即要merge到master之前的分支), 而不是在subtree上。这有助于尽早识别出集成问题并最小化所需的合并工作量。同时也使得安全补丁能够更容易和更快的上线。
自动化系统频繁地运行测试, 通常是在任何代码有改动而进行传递性依赖项的测试(译者注:就是通常所说的回归测试), 虽然这并不总是可行。如果修改没通过测试,系统通常只在几分钟内自动通知作者及其评审人员。大多数团队会通过安装引人瞩目的显示或带有颜色编码光线的雕塑 (绿色代表构建成功和所有测试通过, 红色表示某些测试失败, 黑色表示构建失败) 以非常直观地呈现构建的状态。这种行为有助于将工程师们的注意力集中在“构建处在绿色状态”。大多数规模较大的团队也有一个“构建警察” (这一角色通常由团队成员或是经验更丰富的成员轮流承担),通过与作者合作来快速解决问题或回滚其违规的修改以确保测试在head上的持续通过。即使对于规模非常大的团队,关注持续成功构建,对“head”的开发具有实践价值。
代码所有权。库的每个subtree都可以有一个列出subtree “所有者”用户id的文件。子目录还会从父目录继承所有者,尽管这是选择性地控制。每个subtree的所有者控制其写访问权限(见后面“代码评审”部分中说明)。虽然通常有更多的所有者,但是每个subtree至少需要两个以上,尤其是分布在不同地方的团队。在所有者文件中列出整个团队成员,这是十分常见的。不局限于所有者,Google的任何人均可以subtree进行修改,但他需要某个所有者的许可。这可以确保每一个变化都被理解了软件被修改的工程师审核过。
更多的Google源代码存储库详见[17、18、21],关于另一个大公司又是如何处理面对同样的挑战的, 参见[19]。
2. 构建系统(The Build System)
谷歌使用一个被称为Blaze的分布式构建系统,此系统负责软件编译、链接及其测试。它提供了用来构建和测试软件的标准命令, 并适用于整个代码库。这些标准命令和高度优化的工具意味着任何Google工程师构建版本和测试软件通常是非常简单和快速的。这种一致性在帮助工程师能够跨越项目边界进行修改起着关键作用。
程序员需要为Blaze编写如何完成版本构建的“BUILD”文件。诸如库、程序和测试这些构建实体,均由高级的声明式构建规范(declarative build specifications,DBS)进行声明,描述具体的每一个实体,如实体的名称、源文件、相关的库文件或所依赖的其它实体。这些DBS是由被称为“构建规则”的声明来组成,每个声明均用来指定如“一个拥有依赖于其他库的一系列源文件构成的C++库” 这样的高层次概念,并取决于构建系统将每项构建规则和一系列的构建步骤映射起来,例如为每个源文件进行编译的步骤、链接的步骤,以及确定使用哪个编译器和编译标志。
在某些情况下,特别是Go程序,因为BUILD文件中的依赖性信息 (通常)是源文件依赖信息的抽象,所以构建文件是可以自动生成 (和更新) 的。尽管如此,这些文件也要被检入到代码库中。这就确保了构建系统通过分析构建文件而不是源文件来快速地确定依赖关系,并以此避免了构建系统和编译器或者用于支撑其他编程语言的分析工具之间的过度耦合。
构建系统使用谷歌的分布式计算设施进行实现。通常每个构建的工作是分布在成百上千台机器中穿插进行的。这使得快速构建超大型程序或并行运行成千上万的测试成为可能。
个人构建步骤必须是“密封”的:只取决于其已声明过的输入。
所有依赖项都必须被正确的声明,这是分布式构建的必然诉求:只有已声明的输入信息会被发送到正在运行构建步骤的机器。因此, 可以依据构建系统来获取真实的依赖关系,即使构建系统的某个编译器被视为一种输入。
个人构建步骤是确定的。因此构建系统可以缓存构建结果。软件工程师可以同步其workspace回到原来的变更号再进行重新构建,以得到完全相同的二进制版本。此外,这个缓存可以在不同的用户之间安全地共享。(为了使它正常工作, 我们必须消除构建所调用的工具中的非确定性,例如通过洗掉所生成的输出文件中的时间戳)。
构建系统是可靠的。构建系统跟踪构建规则本身变更上的依赖性,这样如果某操作导致规则变化时依旧知道如何重新构建目标,即使输入对操作是不可知的,例如只有编译器选项发生改变时。同时,也能够妥善处理构建某部分被中断或在构建中修改源文件等各种情形:在这种情况下, 你只需要重新运行构建命令,但从来不需要运行“make clean”这类命令。
构建结果缓存在“在云中”,还包括中间结果。如果另一个构建请求需要相同的结果, 构建系统将自动复用而不是进行重建,即使此请求来自于不同的用户。
快速的增量构建。构建系统一直驻留在内存中,因此可以仅仅针对上次构建之后修改过的文件来进行增量分析来完成新的构建。
预提交检查。针对启动代码审查和/或准备提交变更到存储库时,谷歌有工具自动运行一组测试。每个库subtree都可以包含一个配置文件,以确定要运行哪些测试, 以及是否在代码评审时进行测试、或在提交之前即刻运行测试,或者两者都需要。测试可以是同步的,例如在发送变更给审查之前运行,和/或在提交变更到代码库中之前运行(有利于快速测试)。测试也可以是异步的,即通过邮件将结果发送到评审讨论线程(thread)。[评审线程正是代码审查活动发生的电子邮件线程, 所有线程的信息也会在基于web的代码复查工具中呈现出来。]
3.代码评审(Code review)
谷歌自己开发了优秀的基于Web的代码评审工具,并和邮件系统集成,允许作者发起评审请求、允许评审人员两边对比方式地去浏览代码差异 (带有颜色标注) 并能直接在工具上写评审意见。当代码变更的作者发起代码评审时,评审人员会收到电子邮件通知,并附有链接指向评审工具对应的web页面。当评审人员提交他们的评审意见时,也会发送邮件通知作者。此外, 自动化工具可以发送包含自动化测试或静态分析工具的测试结果等相关通知。
所有源代码主库的变更都必须经过除作者外至少一个以上工程师的审查。此外, 如果作者不是该修改文件的其中的所有者,那么此项变更还需要至少一个所有者的审查和批准。
在特殊情况下,一个库subtree的所有者可以在通过审查前就能发布(提交) 该库subtree的一项紧急的变更,但评审人员必须留名,并且在变更被审核和批准之前,自动此变更的发布者和评审人员仍需继续商议。在这种情况下,由于变更在通过审核前就已经被执行,任何单项的变更都需要明确的评审意见。
对于一个给定的变更,谷歌通过工具来自动推荐出合适的评审人员。工具通过查看发生变更的代码的所有权和作者、近期评审人员的历史、每个潜在的评论人员的待审查的代码数量等信息来实现此功能的。每个库subtree都至少有一个所有者必须审查该项变更的影响并批准这一变更。但除此之外,作者可以自由选择他们认为合适的评审人员。
代码评审中一个潜在的问题是——如果评审人员相应过慢或过于勉强而不批准变更,这可能会降低开发速度。代码的作者选择评审人员时应该有助于避免此类问题,因此允许工程师绕开那些可能会过度拥有代码的评审人员,或把较为简单的变更发送给经验不丰富的评审人员,而相对更复杂的变更则发送给更有经验的评审人员或同时发给多个评审人员。
每个项目的代码评审讨论会被自动拷贝到指定的项目维护者邮件列表中。任何人都可以对任何变更进行自由评论,无论他们是否被提名为此变更的评审人员,同时也不管这条变更是否已经通过了审核。如果发现一个错误,通常会追踪到引入此项错误的变更和原代码的评审意见,并指出问题在哪里,以便让原始作者和评审人员都了解问题所在。
也可以同时发送代码审查给多个评审人员,在其他评审人员给出意见之前,但只要其中有一个评审人员已批准,就尽快提交这项变更(当然,作者和第一个响应的评审人员中至少有一位是所有者),随后的评审意见将在其后依次加入变更序列中。以此来减少评审的周转时间。
除了主库部分(main section of the repository),“实验性”的代码库部分就对正常代码评审没有强制要求。然而,在产品线上运行的代码必须存储在主库中,而且鼓励工程师一开始就在主库的管理下开发代码,而不是先在实验库中研发之后再将其移动到主库中。因为在刚写完代码后就进行代码审查是最有效的,而不是彻底完成代码开发工作之后再进行代码评审。实践中,有些工程师甚至会在实验阶段就请求进行代码评审了。
鼓励工程师每次尽量保持小规模的变更,大的变更也最好是分解成一系列的小变更,以便评审人员很容易地一次性地完成评审。这也使作者在每次评审过程中更容易应对重大变化。非常大规模变更往往太严格,会抵制评审人员所指出的变更。所提倡的保持小型变更的一种方法是——借助代码审核工具给每次变更的规模打上一个文字描述的标签:
对于30~99行的添加/删除/删除等类型的变更,标注为 “medium-size”;
对于300行以上的变更,根据其程度进行标注,如(300~999)为 “large”, (1000 ~1999)为“freakin huge”等等。
(然而,在常见的Google方式中,为了让工作有乐趣,每年会有几天,如“像海盗一样说话”的那天,会使用风趣少见的的描述来代替这些熟悉的描述方式)
( 待续,后面内容更精彩,敬请关注)
作者介绍
Fergus Henderson作为软件工程师已在Google工作了10年以上。在1979年,他还是一个孩子的时候就开始编程,并继续从事编程语言设计和实现上的学术研究。在墨尔本大学,他和他的博士导师共同建立了一个研究小组,开发了编程语言Mercury。他是8个国际会议的程序委员会委员,并发布了超过50万行的开源代码。他是Usenet新闻组comp.std.c ++的前主席,并且是ISO C和C ++委员会官方认可的“技术专家”。他拥有超过15年的商业软件行业经验。在Google,他是Blaze(在整个Google上还在使用的构建工具)的原创开发人员之一,并致力于具有语音识别、语音操作(在Siri!之前)和语音合成功能的服务器端软件开发。他目前管理Google的文字转语音工程团队,但仍然编写和审查大量的代码。他写的软件安装在超过十亿个设备上,每天使用也超过十亿次。